#include "usbd_core.h"

/**
* @brief  USBD_Init
*         Initializes the device stack and load the class driver
* @param  pdev: device instance
* @param  pdesc: Descriptor structure address
* @param  id: Low level core index
* @retval None
*/
USBD_StatusTypeDef USBD_Init(USBD_HandleTypeDef *pdev, USBD_DescriptorsTypeDef *pdesc, uint8_t id)
{
    if(pdev == NULL){
        USBD_ErrLog("Invalid Device handle");
        return USBD_FAIL; 
    }

    if(pdev->pClass != NULL){
        pdev->pClass = NULL;
    }

    if(pdesc != NULL){
        pdev->pDesc = pdesc;
    }

    pdev->dev_state  = USBD_STATE_DEFAULT;
    pdev->id = id;
    pdev->BLSetAddressFlag = 0;

    USBD_DbgLog("\t\tUSBD_LL_Init()\r\n");
    USBD_LL_Init(pdev);

    return USBD_OK;
}

/**
* @brief  USBD_DeInit 
*         Re-Initialize th device library
* @param  pdev: device instance
* @retval status: status
*/
USBD_StatusTypeDef USBD_DeInit(USBD_HandleTypeDef *pdev)
{
    /* Set Default State */
    pdev->dev_state  = USBD_STATE_DEFAULT;

    /* Free Class Resources */
    pdev->pClass->DeInit(pdev, pdev->dev_config);  

    /* Stop the low level driver  */
    USBD_LL_Stop(pdev); 

    /* Initialize low level driver */
    USBD_LL_DeInit(pdev);

    return USBD_OK;
}


/**
  * @brief  USBD_RegisterClass 
  *         Link class driver to Device Core.
  * @param  pDevice : Device Handle
  * @param  pclass: Class handle
  * @retval USBD Status
  */
USBD_StatusTypeDef USBD_RegisterClass(USBD_HandleTypeDef *pdev, USBD_ClassTypeDef *pclass)
{
    USBD_StatusTypeDef   status = USBD_OK;
    
    if(pclass != NULL){
        /* link the class to the USB Device handle */
        pdev->pClass = pclass;
        status = USBD_OK;
    }else{
        USBD_ErrLog("Invalid Class handle");
        status = USBD_FAIL; 
    }

    return status;
}

/**
  * @brief  USBD_Start 
  *         Start the USB Device Core.
  * @param  pdev: Device Handle
  * @retval USBD Status
  */
USBD_StatusTypeDef USBD_Start(USBD_HandleTypeDef *pdev)
{
    /* Start the low level driver  */
    USBD_LL_Start(pdev); 

    return USBD_OK;
}

/**
  * @brief  USBD_Stop 
  *         Stop the USB Device Core.
  * @param  pdev: Device Handle
  * @retval USBD Status
  */
USBD_StatusTypeDef USBD_Stop(USBD_HandleTypeDef *pdev)
{
    /* Free Class Resources */
    pdev->pClass->DeInit(pdev, pdev->dev_config);  

    /* Stop the low level driver  */
    USBD_LL_Stop(pdev); 

    return USBD_OK;  
}

/**
* @brief  USBD_RunTestMode 
*         Launch test mode process
* @param  pdev: device instance
* @retval status
*/
USBD_StatusTypeDef USBD_RunTestMode(USBD_HandleTypeDef  *pdev) 
{
    UNUSED(pdev);
    return USBD_OK;
}


/**
* @brief  USBD_SetClassConfig 
*        Configure device and start the interface
* @param  pdev: device instance
* @param  cfgidx: configuration index
* @retval status
*/

USBD_StatusTypeDef USBD_SetClassConfig(USBD_HandleTypeDef  *pdev, uint8_t cfgidx)
{
    USBD_StatusTypeDef   ret = USBD_FAIL;

    if(pdev->pClass != NULL){
        /* Set configuration  and Start the Class*/
        if(pdev->pClass->Init(pdev, cfgidx) == 0){
            ret = USBD_OK;
        }
    }
    
    return ret; 
}

/**
* @brief  USBD_ClrClassConfig 
*         Clear current configuration
* @param  pdev: device instance
* @param  cfgidx: configuration index
* @retval status: USBD_StatusTypeDef
*/
USBD_StatusTypeDef USBD_ClrClassConfig(USBD_HandleTypeDef  *pdev, uint8_t cfgidx)
{
    /* Clear configuration  and De-initialize the Class process*/
    pdev->pClass->DeInit(pdev, cfgidx);  
    
    return USBD_OK;
}


/**
* @brief  USBD_SetupStage 
*         Handle the setup stage
* @param  pdev: device instance
* @retval status
*/
USBD_StatusTypeDef USBD_LL_SetupStage(USBD_HandleTypeDef *pdev, uint8_t *psetup)
{
    USBD_ParseSetupRequest(&pdev->request, psetup);

    pdev->ep0_state = USBD_EP0_SETUP;
    pdev->ep0_data_len = pdev->request.wLength;

    switch (pdev->request.bmRequest & 0x1F){
        case USB_REQ_RECIPIENT_DEVICE:
            //USBD_DbgLog("StdDevReq\r\n");
            USBD_StdDevReq (pdev, &pdev->request);
            break;

        case USB_REQ_RECIPIENT_INTERFACE:
            //USBD_DbgLog("StdItfReq,needrdy\r\n");
            USBD_StdItfReq(pdev, &pdev->request);
            break;

        case USB_REQ_RECIPIENT_ENDPOINT:
            //USBD_DbgLog("StdEPReq,needrdy\r\n");
            USBD_StdEPReq(pdev, &pdev->request);
            break;

        default:
            USBD_DbgLog("USBD_LL_SetupStage_stall\r\n");
            USBD_LL_StallEP(pdev , pdev->request.bmRequest & 0x80);
            break;
    }  

    return USBD_OK;  
}


/**
* @brief  USBD_DataOutStage 
*         Handle data OUT stage
* @param  pdev: device instance
* @param  epnum: endpoint index
* @retval status
*/
USBD_StatusTypeDef USBD_LL_DataOutStage(USBD_HandleTypeDef *pdev , uint8_t epnum, uint8_t *pdata)
{
    USBD_EndpointTypeDef    *pep;

    if(epnum == 0){
        pep = &pdev->ep_out[0];

        if ( pdev->ep0_state == USBD_EP0_DATA_OUT){
            if(pep->rem_length > pep->maxpacket){
                pep->rem_length -=  pep->maxpacket;
                USBD_CtlContinueRx (pdev, 
                pdata,
                MIN(pep->rem_length ,pep->maxpacket));
                //USBD_DbgLog("o4_rdy\r\n");
            }else{
                if((pdev->pClass->EP0_RxReady != NULL)&&(pdev->dev_state == USBD_STATE_CONFIGURED)){
                    pdev->pClass->EP0_RxReady(pdev);
                }
                USBD_CtlSendStatus(pdev);
                //USBD_DbgLog("i5_rdy\r\n");
            }
        }
    }else if((pdev->pClass->DataOut != NULL)&&(pdev->dev_state == USBD_STATE_CONFIGURED)){
        pdev->pClass->DataOut(pdev, epnum); 
    }else{
        /* should never be in this condition */
        return USBD_FAIL;
    }
    
    return USBD_OK;
}

/**
* @brief  USBD_DataInStage 
*         Handle data in stage
* @param  pdev: device instance
* @param  epnum: endpoint index
* @retval status
*/
USBD_StatusTypeDef USBD_LL_DataInStage(USBD_HandleTypeDef *pdev ,uint8_t epnum, uint8_t *pdata)
{
    USBD_EndpointTypeDef *pep;

    if(epnum == 0) {
        pep = &pdev->ep_in[0];

        if ( pdev->ep0_state == USBD_EP0_DATA_IN){
            if(pep->rem_length > pep->maxpacket){
                pep->rem_length -=  pep->maxpacket;
                USBD_CtlContinueSendData (pdev, pdata, pep->rem_length);

                /* Prepare endpoint for premature end of transfer */
                USBD_LL_PrepareReceive (pdev, 0, NULL, 0);
                //USBD_DbgLog("i_rdy\r\n");
            }else{
                /* last packet is MPS multiple, so send ZLP packet */
                if((pep->total_length % pep->maxpacket == 0) && (pep->total_length >= pep->maxpacket) && (pep->total_length < pdev->ep0_data_len )){
                    USBD_CtlContinueSendData(pdev , NULL, 0);
                    pdev->ep0_data_len = 0;

                    /* Prepare endpoint for premature end of transfer */
                    USBD_LL_PrepareReceive (pdev, 0, NULL, 0);
                    //USBD_DbgLog("i_rdy\r\n");
                }else{
                    if((pdev->pClass->EP0_TxSent != NULL)&&(pdev->dev_state == USBD_STATE_CONFIGURED)){
                        pdev->pClass->EP0_TxSent(pdev); 
                    }
                    USBD_CtlReceiveStatus(pdev);
                    //USBD_DbgLog("o_rdy\r\n");
                }
            }
        }
        if (pdev->dev_test_mode == 1){
            USBD_RunTestMode(pdev); 
            pdev->dev_test_mode = 0;
        }
        if(pdev->BLSetAddressFlag==1){/* add this to test setaddr in packet */
            USB_Set_Device_Addr(pdev->dev_address);
            pdev->BLSetAddressFlag = 0;
        }
    }else if((pdev->pClass->DataIn != NULL)&&(pdev->dev_state == USBD_STATE_CONFIGURED)){
        pdev->pClass->DataIn(pdev, epnum);
    }else{
        /* should never be in this condition */
        return USBD_FAIL;
    }
    
    return USBD_OK;
}

/**
* @brief  USBD_LL_Reset 
*         Handle Reset event
* @param  pdev: device instance
* @retval status
*/
USBD_StatusTypeDef USBD_LL_Reset(USBD_HandleTypeDef  *pdev)
{
    /* Open EP0 OUT */
    USBD_LL_OpenEP(pdev, 0x00, USBD_EP_TYPE_CTRL, USB_MAX_EP0_SIZE);
    pdev->ep_out[0].maxpacket = USB_MAX_EP0_SIZE;

    USBD_LL_OpenEP(pdev, 0x80, USBD_EP_TYPE_CTRL, USB_MAX_EP0_SIZE);
    pdev->ep_in[0].maxpacket = USB_MAX_EP0_SIZE;

    /* Upon Reset call user call back */
    pdev->dev_state = USBD_STATE_DEFAULT;

    pdev->BLSetAddressFlag = 0;

    if (pdev->pClassData){
        pdev->pClass->DeInit(pdev, pdev->dev_config);  
    }

  return USBD_OK;
}



/**
* @brief  USBD_LL_Reset 
*         Handle Reset event
* @param  pdev: device instance
* @retval status
*/
USBD_StatusTypeDef USBD_LL_SetSpeed(USBD_HandleTypeDef  *pdev, USBD_SpeedTypeDef speed)
{
    pdev->dev_speed = speed;
    
    return USBD_OK;
}


/**
* @brief  USBD_Suspend 
*         Handle Suspend event
* @param  pdev: device instance
* @retval status
*/
USBD_StatusTypeDef USBD_LL_Suspend(USBD_HandleTypeDef  *pdev)
{
    pdev->dev_old_state =  pdev->dev_state;
    pdev->dev_state  = USBD_STATE_SUSPENDED;
    
    return USBD_OK;
}

/**
* @brief  USBD_Resume 
*         Handle Resume event
* @param  pdev: device instance
* @retval status
*/
USBD_StatusTypeDef USBD_LL_Resume(USBD_HandleTypeDef  *pdev)
{
    if (pdev->dev_state == USBD_STATE_SUSPENDED)
    {
        pdev->dev_state = pdev->dev_old_state;
    }
    
    return USBD_OK;
}

/**
* @brief  USBD_SOF 
*         Handle SOF event
* @param  pdev: device instance
* @retval status
*/
USBD_StatusTypeDef USBD_LL_SOF(USBD_HandleTypeDef  *pdev)
{
    if(pdev->dev_state == USBD_STATE_CONFIGURED){
        if(pdev->pClass->SOF != NULL){
            pdev->pClass->SOF(pdev);
        }
    }
    
    return USBD_OK;
}

/**
* @brief  USBD_IsoINIncomplete 
*         Handle iso in incomplete event
* @param  pdev: device instance
* @retval status
*/
USBD_StatusTypeDef USBD_LL_IsoINIncomplete(USBD_HandleTypeDef  *pdev, uint8_t epnum)
{
    UNUSED(pdev);
    UNUSED(epnum);
    return USBD_OK;
}

/**
* @brief  USBD_IsoOUTIncomplete 
*         Handle iso out incomplete event
* @param  pdev: device instance
* @retval status
*/
USBD_StatusTypeDef USBD_LL_IsoOUTIncomplete(USBD_HandleTypeDef  *pdev, uint8_t epnum)
{
    UNUSED(pdev);
    UNUSED(epnum);
    return USBD_OK;
}

/**
* @brief  USBD_DevConnected 
*         Handle device connection event
* @param  pdev: device instance
* @retval status
*/
USBD_StatusTypeDef USBD_LL_DevConnected(USBD_HandleTypeDef  *pdev)
{
    UNUSED(pdev);
    return USBD_OK;
}

/**
* @brief  USBD_DevDisconnected 
*         Handle device disconnection event
* @param  pdev: device instance
* @retval status
*/
USBD_StatusTypeDef USBD_LL_DevDisconnected(USBD_HandleTypeDef  *pdev)
{
    /* Free Class Resources */
    pdev->dev_state = USBD_STATE_DEFAULT;
    pdev->pClass->DeInit(pdev, pdev->dev_config);  

    return USBD_OK;
}
